// Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.

// 弹性伸缩策略服务
package service

import (
	"encoding/json"
	"fmt"
	"github.com/beego/beego/v2/client/orm"
	"github.com/google/uuid"
	pkgerror "github.com/pkg/errors"
	"net/http"
	"scase.io/application-auto-scaling-service/pkg/api/errors"
	"scase.io/application-auto-scaling-service/pkg/api/model"
	"scase.io/application-auto-scaling-service/pkg/cloudresource"
	"scase.io/application-auto-scaling-service/pkg/common"
	"scase.io/application-auto-scaling-service/pkg/db"
	"scase.io/application-auto-scaling-service/pkg/metricmonitor"
	"scase.io/application-auto-scaling-service/pkg/service/taskservice"
	"scase.io/application-auto-scaling-service/pkg/utils/logger"
)

// CreateScalingPolicy create scaling policy
func CreateScalingPolicy(log *logger.FMLogger, projectId string,
	req model.CreateScalingPolicyReq) (*model.CreateScalingPolicyResp, *errors.ErrorResp) {
	policyId := uuid.NewString()
	policy, err := convertCreateScalingPolicyReq(req, projectId, policyId)
	if err != nil {
		log.Error("Convert CreateScalingPolicyReq err: %+v", err)
		return nil, errors.NewErrorRespWithHttpCode(errors.ServerInternalError, http.StatusInternalServerError)
	}
	if err := db.AddScalingPolicy(policy); err != nil {
		log.Error("Add ScalingPolicy to db err: %+v", err)
		return nil, errors.NewErrorRespWithHttpCode(errors.ServerInternalError, http.StatusInternalServerError)
	}
	return &model.CreateScalingPolicyResp{ScalingPolicyId: policyId}, nil
}

// 绑定策略和伸缩组，并新建监控任务
func BindingScalingPolicy(log *logger.FMLogger, projectId, fleetId, policyId string) *errors.ErrorResp {
	// 1. 添加关联表，不可重复插入
	groupId, err := db.ScalingGroupTable().GetGroupIdByFleetId(fleetId)
	if err != nil {
		log.Error("get group id by fleet id [%s] error:+v", fleetId, err)
		return errors.NewErrorRespWithMessage(errors.ServerInternalError, fmt.Sprintf("error:%+v", err))
	}
	policy, err := db.GetScalingPolicyById(projectId, policyId)
	if err != nil {
		log.Error("Get policy [%s] error:%+v", policyId, err)
		return errors.NewErrorRespWithMessage(errors.ServerInternalError, fmt.Sprintf("error:%+v", err))
	}
	if db.GroupPolicyRelationTable().CheckRelationExist(groupId, "") {
		log.Error("the fleet[%s] has bind policy", fleetId)
		return errors.NewErrorRespWithMessage(errors.ServerInternalError,
			fmt.Sprintf("the fleet[%s] has bind policy", fleetId))
	}
	if err := db.GroupPolicyRelationTable().InsertRelation(fleetId, groupId, policyId); err != nil {
		log.Error("insert fleet[%s]-policy[%s] relation error:%+v", fleetId, policyId, err)
		return errors.NewErrorRespWithMessage(errors.ServerInternalError, fmt.Sprintf("error:%+v", err))
	}
	// 2. 添加监控任务
	targetConfig := model.TargetConfiguration{}
	if err := json.Unmarshal([]byte(policy.PolicyConfig), &targetConfig); err != nil {
		log.Error("parse target config of policy [%s] error:%+v", policyId, err)
		return errors.NewErrorRespWithMessage(errors.ServerInternalError, fmt.Sprintf("error:%+v", err))
	}
	task, err := metricmonitor.GetMgmt().NewTaskForPolicy(groupId, policy.Id, *targetConfig.MetricName, *targetConfig.MaxTargetValue, *targetConfig.MinTargetValue)
	if err != nil {
		log.Error("Creat metric monitor task for policy[%s] err: %+v", policy.Id, err)
		return errors.NewErrorRespWithMessage(errors.ServerInternalError, fmt.Sprintf("error:%+v", err))
	}
	err = metricmonitor.GetMgmt().AddTask(task)
	if err != nil {
		log.Error("Add metric monitor task for policy[%s] : %+v", policy.Id, err)
		return errors.NewErrorRespWithMessage(errors.ServerInternalError, fmt.Sprintf("error:%+v", err))
	}
	return nil
}

// 解绑策略，并删除对应监控任务
func UnBindScalingPolicy(log *logger.FMLogger, fleetId, policyId string) *errors.ErrorResp {
	// 1. 删除关联
	groupId, err := db.ScalingGroupTable().GetGroupIdByFleetId(fleetId)
	if err != nil {
		log.Error("get group id by fleet id [%s] error:+v", fleetId, err)
		return errors.NewErrorRespWithMessage(errors.ServerInternalError, fmt.Sprintf("error:%+v", err))
	}
	group, err := db.ScalingGroupTable().GetScalingGroupById(groupId)
	if err != nil {
		log.Error("get group info by id: %s error:%+v", groupId, err)
		return errors.NewErrorRespWithMessage(errors.ServerInternalError, fmt.Sprintf("error:%+v", err))
	}
	if group.EnableAutoScaling == true {
		log.Error("scaling group [%s] of fleet %s is auto scaling, cannot unbind", groupId, group.FleetId)
		return errors.NewErrorRespWithMessage(errors.ServerInternalError, fmt.Sprintf("fleet %s is auto scaling, cannot unbind from policy", group.FleetId))
	}
	if err := db.GroupPolicyRelationTable().DeleteRelation(groupId, policyId); err != nil {
		log.Error("delete fleet[%s]-policy[%s] relation error:%+v", fleetId, policyId)
		return errors.NewErrorRespWithMessage(errors.ServerInternalError, fmt.Sprintf("error:%+v", err))
	}
	// 2. 删除监控任务
	monitorTask, err := db.GetMetricMonitorTaskByScalingPolicy(policyId)
	if err != nil {
		if !pkgerror.Is(err, orm.ErrNoRows) {
			log.Error("[delete_group] get policy error:%+v", err)
			return errors.NewErrorRespWithMessage(errors.ServerInternalError, fmt.Sprintf("error:%+v", err))
		}
	}
	if monitorTask != nil {
		if err := db.DeleteMetricMonitorTask(monitorTask.Id); err != nil {
			log.Error("[delete_group] delete monitor task [%s] error:%+v", monitorTask.Id, err)
			return errors.NewErrorRespWithMessage(errors.ServerInternalError, fmt.Sprintf("error:%+v", err))
		}
	}
	return nil
}

func ListBindRelation(log *logger.FMLogger, fleetId, groupId, policyId string, limit, offset int) (*model.ListRelationResp, *errors.ErrorResp) {
	relationList, err := db.GroupPolicyRelationTable().GetRelation(fleetId, groupId, policyId, limit, offset)
	if err != nil {
		log.Error("get relation of fleet [%s], groupId [%s], policy[%s] error:%+v", fleetId, groupId, policyId, err)
		return nil, errors.NewErrorRespWithMessage(errors.ServerInternalError, fmt.Sprintf("error:%+v", err))
	}
	ListRelationResp := model.ListRelationResp{
		Count: len(relationList),
	}
	for _, relation := range relationList {
		ListRelationResp.Relation = append(ListRelationResp.Relation, model.Relation{
			ScalingPolicyId: relation.ScalingPolicyId,
			ScalingGroupId:  relation.ScalingGroupId,
			FleetId:         relation.FleetId,
		})
	}
	return &ListRelationResp, nil
}

// DeleteScalingPolicy delete scaling policy
func DeleteScalingPolicy(log *logger.FMLogger, projectId, policyId string) *errors.ErrorResp {
	if exist := db.IsScalingPolicyExist(projectId, policyId); !exist {
		log.Error("The scaling policy[%s] of project[%s] is not found", policyId, projectId)
		return errors.NewErrorRespWithHttpCode(errors.ScalingPolicyNotFound, http.StatusNotFound)
	}
	policy, err := db.GetScalingPolicyById(projectId, policyId)
	if err != nil {
		log.Error("Read scaling policy[%s] of project[%s] from db err: %+v", policyId, projectId, err)
		return errors.NewErrorRespWithHttpCode(errors.ServerInternalError, http.StatusInternalServerError)
	}
	// 已绑定，则不能删除策略
	if db.GroupPolicyRelationTable().CheckRelationExist("", policyId) {
		log.Error("the policy[%s] has bind scaling group", policyId)
		return errors.NewErrorRespWithMessage(errors.ServerInternalError,
			fmt.Sprintf("the policy[%s] has bind scaling group", policyId))
	}
	// 如果有任务则删除
	taskId := metricmonitor.GetMgmt().TaskIdForPolicy(policy.Id)
	if len(taskId) != 0 {
		if err = metricmonitor.GetMgmt().DeleteTask(taskId); err != nil {
			log.Error("Delete metric monitor task for policy[%s] err: %+v", policy.Id, err)
			return errors.NewErrorRespWithHttpCode(errors.ServerInternalError, http.StatusInternalServerError)
		}
	}

	// 如果有绑定的group则不能删除
	groups, err := db.GetBindingGroupByPolicy(policyId)
	if err != nil {
		log.Error("Read binding groups of scaling policy[%s] from db err: %+v", policyId, err)
		return errors.NewErrorRespWithHttpCode(errors.ServerInternalError, http.StatusInternalServerError)
	}
	if len(groups) > 0 {
		log.Error("The policy [%s] has bound groups [%v]", policyId, groups)
		return errors.NewErrorRespWithMessage(errors.ServerInternalError,
			fmt.Sprintf("The policy [%s] has bound groups [%v]", policyId, groups))
	}
	if err = db.DeleteScalingPolicy(projectId, policyId); err != nil {
		log.Error("Delete scaling policy[%s] of project[%s] from db err: %+v", policyId, projectId, err)
		return errors.NewErrorRespWithHttpCode(errors.ServerInternalError, http.StatusInternalServerError)
	}
	return nil
}

// UpdateScalingPolicy update scaling policy
func UpdateScalingPolicy(log *logger.FMLogger, projectId, policyId string,
	req model.UpdateScalingPolicyReq) *errors.ErrorResp {
	if exist := db.IsScalingPolicyExist(projectId, policyId); !exist {
		log.Error("The scaling policy[%s] of project[%s] is not found", policyId, projectId)
		return errors.NewErrorRespWithHttpCode(errors.ScalingPolicyNotFound, http.StatusNotFound)
	}
	policy, err := db.GetScalingPolicyById(projectId, policyId)
	if err != nil {
		log.Error("Read scaling policy[%s] of project[%s] from db err: %+v", policyId, projectId, err)
		return errors.NewErrorRespWithHttpCode(errors.ServerInternalError, http.StatusInternalServerError)
	}
	err = convertUpdateScalingPolicyReq(req, policy)
	if err != nil {
		log.Error("Convert UpdateScalingPolicyReq of policy[%s] err: %+v", policyId, err)
		return errors.NewErrorRespWithHttpCode(errors.ServerInternalError, http.StatusInternalServerError)
	}
	if req.TargetConfiguration != nil {
		taskId := metricmonitor.GetMgmt().TaskIdForPolicy(policy.Id)
		if len(taskId) != 0 {
			if err = metricmonitor.GetMgmt().UpdateTask(taskId, *req.TargetConfiguration.MaxTargetValue, *req.TargetConfiguration.MinTargetValue); err != nil {
				log.Error("Update metric monitor task for policy[%s] err: %+v", policy.Id, err)
				return errors.NewErrorRespWithHttpCode(errors.ServerInternalError, http.StatusInternalServerError)
			}
		}
	}
	if err := db.UpdateScalingPolicyById(policyId, policy); err != nil {
		log.Error("Update ScalingPolicy from db err: %+v", err)
		return errors.NewErrorRespWithHttpCode(errors.ServerInternalError, http.StatusInternalServerError)
	}
	return nil
}

// scalingJudgmentForDeleteScalingPolicy 删除策略后，判断是否触发弹性伸缩：
//  1. 伸缩组的EnableAutoScaling为false, 伸缩组仍按DesireInstanceNumber进行弹性伸缩，不触发弹性伸缩；
//  2. 伸缩组的EnableAutoScaling为true 且 伸缩组存在其他伸缩策略，伸缩组仍按伸缩策略进行弹性伸缩，不触发弹性伸缩；
//  3. 伸缩组的EnableAutoScaling为true 且 伸缩组不存在其他伸缩策略，
//     伸缩组由按伸缩策略改为按DesireInstanceNumber进行弹性伸缩，可能触发弹性伸缩。
func scalingJudgmentForDeleteScalingPolicy(log *logger.FMLogger, group *db.ScalingGroup) error {
	resCtrl, err := cloudresource.GetResourceController(group.ProjectId)
	if err != nil {
		return err
	}
	if group.EnableAutoScaling == false {
		return nil
	}
	vmGroup, err := db.GetVmScalingGroupById(group.ResourceId)
	if err != nil {
		return err
	}
	curNum, err := resCtrl.GetAsGroupCurrentInstanceNum(vmGroup.AsGroupId)
	log.Info("instance scaling group[%s]: curInstanceNum is %d, desireInstanceNumber is %d",
		group.Id, curNum, group.DesireInstanceNumber)
	if curNum > group.DesireInstanceNumber {
		return taskservice.StartScaleInGroupTaskForRandomVms(group.Id, group.ProjectId,
			curNum-group.DesireInstanceNumber)
	} else if curNum < group.DesireInstanceNumber {
		return taskservice.StartScaleOutGroupTask(group.Id, group.DesireInstanceNumber)
	}
	log.Info("The current number of instances is equal to desireInstanceNumber")
	return nil
}

func scalingJudgmentForDeleteScalingGroupPolicy(log *logger.FMLogger, group *db.ScalingGroup) error {
	exist := db.GroupPolicyRelationTable().CheckRelationExist(group.Id, "")
	if group.EnableAutoScaling == false || !exist {
		return nil
	}
	if group.CurrentInstanceNumber > group.DesireInstanceNumber {
		if err := taskservice.StartEcsScaleInGroupTaskForRandomVms(group, group.CurrentInstanceNumber-group.DesireInstanceNumber); err != nil {
			if pkgerror.Is(err, common.ErrNoNeedToDo) {
				return nil
			}
			return err
		}
	} else if group.CurrentInstanceNumber < group.DesireInstanceNumber {
		return taskservice.StartEcsScalingOutTask(group, group.DesireInstanceNumber-group.CurrentInstanceNumber)
	}
	log.Info("The current number of instances is equal to desireInstanceNumber")
	return nil
}
